﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using MusicStore.Wpf.Toolkit.Commands;

namespace MusicStore.Dashboard.Client.Commands
{
  public class CommandGroup : ItemsControl, ICommand
  {
    private EventHandler mCanExecuteChanged;

    public CommandGroup()
    {
      Loaded += OnLoaded;
    }

    #region Dependency properties

    public static readonly DependencyProperty WaitPanelProperty =
      DependencyProperty.Register("WaitPanel", typeof(Panel), typeof(CommandGroup), null);

    public Panel WaitPanel
    {
      get { return (Panel) GetValue(WaitPanelProperty); }
      set { SetValue(WaitPanelProperty, value); }
    }

    public static readonly DependencyProperty IsExecutingProperty =
      DependencyProperty.Register("IsExecuting", typeof(bool), typeof(CommandGroup), new PropertyMetadata(default(bool)));

    public bool IsExecuting
    {
      get { return (bool) GetValue(IsExecutingProperty); }
      set { SetValue(IsExecutingProperty, value); }
    }

    #endregion

    #region Properties

    public bool HasWaitPanel
    {
      get { return WaitPanel != null; }
    }

    #endregion

    #region IDisposable

    public void Dispose()
    {
      try
      {
        Dispose(true);
      }
      finally
      {
        GC.SuppressFinalize(this);
      }
    }

    ~CommandGroup()
    {
      Dispose(false);
    }

    protected void Dispose(Boolean disposing)
    {
      if (disposing)
      {
        Loaded -= OnLoaded;
      }
    }

    #endregion

    #region Events

    protected void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
    {
      if (DesignerProperties.GetIsInDesignMode(this)) return;
    }

    public event EventHandler<CancelEventArgs> Executing;

    public void RaiseExecuting(CancelEventArgs cancelEventArgs)
    {
      EventHandler<CancelEventArgs> handler = Executing;
      if (handler != null) handler(this, cancelEventArgs);
    }

    public event EventHandler<CommandExecutionResultEventArgs> Executed;

    public void RaiseExecuted(CommandExecutionResult commandExecutionResult)
    {
      EventHandler<CommandExecutionResultEventArgs> handler = Executed;
      if (handler != null) handler(this, new CommandExecutionResultEventArgs(commandExecutionResult));
    }

    protected virtual void OnExecuted(CommandExecutionResult commandExecutionResult)
    {
    }

    protected void InvalidateCanExecute()
    {
      if (mCanExecuteChanged != null)
      {
        mCanExecuteChanged(this, EventArgs.Empty);
      }
    }

    public event EventHandler CanExecuteChanged
    {
      add { mCanExecuteChanged += value; }
      remove { mCanExecuteChanged -= value; }
    }

    protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
    {
      base.OnPropertyChanged(e);
      InvalidateCanExecute();
    }

    #endregion

    protected void SetComplete()
    {
      OnExecutedCore(CommandExecutionResult.FromSuccess());
    }

    private void SetFailure(Exception exception)
    {
      OnExecutedCore(CommandExecutionResult.FromException(exception));
    }

    internal void OnExecutedCore(CommandExecutionResult commandExecutionResult)
    {
      try
      {
        try
        {
          OnExecuted(commandExecutionResult);
        }
        catch (Exception e)
        {
          SetFailure(e);
          RaiseExecuted(CommandExecutionResult.FromException(e));
        }
        RaiseExecuted(commandExecutionResult);
      }
      catch (Exception e)
      {
        SetFailure(e);
        RaiseExecuted(CommandExecutionResult.FromException(e));
      }
      finally
      {
        IsExecuting = false;
      }
    }

    internal bool OnExecutingCore()
    {
      var cancelEventArgs = new CancelEventArgs();
      cancelEventArgs.Cancel = !OnExecuting();
      RaiseExecuting(cancelEventArgs);
      IsExecuting = !cancelEventArgs.Cancel;
      return IsExecuting;
    }

    protected virtual bool OnExecuting()
    {
      return true;
    }

    public override string ToString()
    {
      throw new NotImplementedException();
    }

    #region Nested type: CommandExecutionResultEventArgs

    public class CommandExecutionResultEventArgs : EventArgs
    {
      private readonly CommandExecutionResult mCommandExecutionResult;

      public CommandExecutionResultEventArgs(CommandExecutionResult commandExecutionResult)
      {
        mCommandExecutionResult = commandExecutionResult;
      }

      public CommandExecutionResult CommandExecutionResult
      {
        get { return mCommandExecutionResult; }
      }
    }

    #endregion

    #region ICommand Members

    public Boolean CanExecute(Object parameter)
    {
      return CanExecute() && !IsExecuting;
    }

    public void Execute(Object parameter)
    {
      try
      {
        if (!CanExecute(parameter)) return;

        if (OnExecutingCore())
        {
          Execute();
        }
      }
      catch (Exception exception)
      {
        OnExecutedCore(CommandExecutionResult.FromException(exception));
      }
    }

    #endregion

    #region Overrides of CommandBase

    private int mActiveCall;

    public override void OnApplyTemplate()
    {
      base.OnApplyTemplate();
      IEnumerator wLogicalChildren = LogicalChildren;
      Debug.Assert(wLogicalChildren != null, "wLogicalChildren != null");
      wLogicalChildren.Reset();
      while (wLogicalChildren.MoveNext())
      {
        var commandItem = wLogicalChildren.Current as CommandItem;
        if (commandItem != null)
        {
          CommandCore wCommand = commandItem.Command;
          var wCallBackHandler = new CallBackHandler(this);
          wCommand.Executing += wCallBackHandler.OnExecuting;
          wCommand.Executed += wCallBackHandler.OnExecuted;
        }
      }

    }

    protected bool CanExecute()
    {
      return true;
    }

    protected void Execute()
    {
      IEnumerator wLogicalChildren = LogicalChildren;
      Debug.Assert(wLogicalChildren != null, "wLogicalChildren != null");
      wLogicalChildren.Reset();
      while (wLogicalChildren.MoveNext())
      {
        var commandItem = wLogicalChildren.Current as CommandItem;
        if (commandItem != null)
        {
          CommandCore wCommand = commandItem.Command;
          ((ICommand)wCommand).Execute(null);
        }
      }
    }

    private class CallBackHandler
    {
      private readonly CommandGroup mCommandGroup;
      private readonly ICommand mCommand;

      public CallBackHandler(CommandGroup commandGroup)
      {
        mCommandGroup = commandGroup;
      }

      public void OnExecuting(object sender, CancelEventArgs e)
      {
        Interlocked.Increment(ref mCommandGroup.mActiveCall);
        mCommandGroup.IsExecuting = mCommandGroup.mActiveCall > 0;
      }

      public void OnExecuted(object sender, EventArgs eventArgs)
      {
        Interlocked.Decrement(ref mCommandGroup.mActiveCall);
        mCommandGroup.IsExecuting = mCommandGroup.mActiveCall > 0;
      }
    }


    #endregion
  }

  public class CommandItem : FrameworkElement
  {
    public static readonly DependencyProperty CommandProperty =
      DependencyProperty.Register("Command", typeof(CommandCore), typeof(CommandItem), null);

    public CommandCore Command
    {
      get { return (CommandCore) GetValue(CommandProperty); }
      set { SetValue(CommandProperty, value); }
    }
  }
}